<?php
/**
 * 变量处理类
 * 
 * 支持的变量有：
 * <code>
 * $var
 * $var[a]
 * $var->b->c->d
 * $var.b.c.d
 * $var->b[a]
 * $var+1
 * $var[a]+1
 * $var->b[a]+1
 * $var[a][b][c][d]
 * </code>
 * 
 * 
 * 从0.0.3开始起，可以对数组和字符串使用$var[m,n]来截取字符：
 * - $array[1,3] 截取从1到3的元素，组成新的数组
 * - $string[1,3] 截取从1到3的字符，组成新的字符串 
 * 
 * @version $Id: IMintVariable.php 312 2008-04-23 16:29:38Z iwind $
 * @package util.mint.element
 * @since 0.0.2
 */
class IMintVariable extends IObject {
	private $description;
	private $valid = true;
	
	private $mathRegexp;
	private $mathOperatorRegexp;
	private $varRegexp;
	private $arrayRegexp;
	private $arraySliceRegexp;
	private $objectRegexp;
	private $dotObjectRegexp;
	private $varAndMathRegexp;
	private $mixedVarRegexp;
	private $mixedVarAndMathRegexp;
	
	private $parseProcess;
	private $varname;
	
	private $modifierArgRegexp;
	private $modifierFuncRegexp;
	
	private $wholeVariableRegexp;
	
	/**
	 * 构造器
	 *
	 * @param string $description 变量描述，可支持$a.b.c.d["e.f"]->g->h["i"][2,3][1]+3.14|slice(1,2)|cat("mysql")如此复杂的变量和修饰符定义
	 * @since 0.0.3
	 */
	public function __construct($description) {
		$this->description = $description;
		
		$this->initRegexps();
		
		//第一位是否为[1..100]
		$match = array();
		if (preg_match("/^\[(\-?\d+)\.\.(\-?\d+)\]$/", $description, $match)) {
			$this->varname = "\$" . $this->getUniqueId();
			$this->parseProcess = $this->varname . "=range(" . $match[1] . "," . $match[2] . ");\n";
			return;
		}
		
		//第一位是否类似于为[a..z]
		if (preg_match("/^\[([a-z])\.\.([a-z])\]$/i", $description, $match)) {
			$this->varname = "\$" . $this->getUniqueId();
			$this->parseProcess = $this->varname . "=range('" . $match[1] . "','" . $match[2] . "');\n";
			return;
		}
		
		//是否为json
		if (preg_match("/^json:(.+)$/i", $description, $match)) {
			$this->varname = "\$" . $this->getUniqueId();
			$this->parseProcess = $this->varname . "=json_decode(\"" . addslashes($match[1]) . "\", true);\n";
			return;
		}
		
		/**
		 * Array
			(
			    [0] => *$a.b.c.d["e.f"]->g->h["i"][2,3][1]+3.14|slice(1,2)|cat("mysql")
			    [1] => *
			    [2] => a.b.c.d["e.f"]->g->h["i"][2,3][1]+3.14
			    [3] => 
			    [4] => |slice(1,2)|cat("mysql")
			)
		 */
		$pieces = array();
		
		foreach (IMintConfig::$variableMarkArray as $mark) {
			$mark = preg_quote($mark, "/");
			$pieces1 = $pieces2 = array();
			$containsVar1 = preg_match("/^(\\*?){$mark}(" . $this->mixedVarAndMathRegexp . ")(.*)$/i", $description, $pieces1);
			$containsVar2 = preg_match("/^(\\*?){$mark}\\{(" . $this->mixedVarAndMathRegexp . ")\\}(.*)$/i", $description, $pieces2);			
			if ($containsVar1) {
				$pieces = $pieces1;
			} 
			else if ($containsVar2) {
				$pieces = $pieces2;
			}
		}
		
		if (empty($pieces)) {
			$this->valid = false;
			return;
		}
		
		
		//变量值 a.b.c.d["e.f"]->g->h["i"][2,3][1]+3.14|slice(1,2)|cat("mysql")
		$varDescription = $pieces[2];
		if (preg_match("/^TDataSet/", $varDescription)) {
			$this->valid = false;
			return;
		}
		$fragments = $this->getFragments($varDescription);
		$this->varname = "\$" . $this->getUniqueId();
		$this->parseProcess .= $this->varname . "=\$TDataSet->" . implode("", $fragments) . ";\n";
		
		//modifiers 
		/**
		 * |slice(1,2)|cat("mysql","\"mssql")
		 */
		$modifierString = $pieces[4];
		
		
		$modifierRegexp = "\\|(" . $this->modifierFuncRegexp . "(?:\\((?:[,]?" . $this->modifierArgRegexp . ")*\\))?" . ")";
		//echo "/^((?:" . $modifierRegexp . ")*)((?:" . $this->mathOperatorRegexp . $this->mathRegexp .  ")*)$/iU<br/>";
		$match = array();
		if (!preg_match("/^((?:" . $modifierRegexp . ")*)((?:" . $this->mathOperatorRegexp . $this->mathRegexp .  ")*)$/iU", $modifierString, $match)) {
			$this->valid = false;
			return;
		}
		$modifierString = $match[1];
		if ($modifierString != null) {
			$math = $match[5];
			$match = array();
			preg_match_all("/()\|(" . $this->modifierFuncRegexp . "(?:\((?:[,]?" . $this->modifierArgRegexp . ")*\))?" . ")/i", $modifierString, $match);
			foreach ($match[3] as $index=>$modifierName) {
				$match2 = array();
				preg_match("/" . $this->modifierFuncRegexp . "(?:\((.*)\))?$/i", $match[2][$index], $match2);//"mysql",'mssql','sql server'
				$arguments = "";
				if (isset($match2[2])) {
					$arguments = implode(",", $this->getArguments($match2[2]));
				}
				$this->parseProcess .= $this->varname . "=IMintModifierExecutor::execute(\"" . addslashes($modifierName) . "\", " . $this->varname . ", array({$arguments}));\n";
			}
			$this->parseProcess .= $this->varname . "=" . $this->varname. $math . ";\n";
		}
		
		$this->wholeVariableRegexp = $this->mixedVarAndMathRegexp . "(?:\|(?:" . $this->modifierFuncRegexp . "(?:\((?:[,]?" . $this->modifierArgRegexp . ")*\))?" . "))*(" . $this->mathOperatorRegexp . $this->mathRegexp . ")*";
	}
	
	/**
	 * 初始化匹配模式
	 *
	 * @since 0.0.3
	 */
	private function initRegexps() {
		$this->mathRegexp = "[\-\+]?(?:\d+\.)?\d+";// -2.45
		$this->mathOperatorRegexp = "\s*[\+\-\*%\/]\s*"; // + - * / %
		$this->varRegexp = "[a-z_]\w*";// $var
		$this->arrayRegexp = "\[([\"\']?)[\.\-\w]+(?:\\3)\]"; // $var[a][b]
		$this->arraySliceRegexp = "\[\s*\d+\s*\,\s*\d+\s*\]";
		$this->objectRegexp = "->" . $this->varRegexp; // $var->a->b
		$this->dotObjectRegexp = "\." . $this->varRegexp; // $var.a.b
		$this->varAndMathRegexp = $this->mathOperatorRegexp . $this->mathRegexp; // $var+1.2*3.14
		$this->mixedVarRegexp =  $this->arrayRegexp . "|" . $this->arraySliceRegexp . "|" . $this->objectRegexp . "|" . $this->dotObjectRegexp;
		$this->mixedVarAndMathRegexp = $this->varRegexp . "(?:" . $this->mixedVarRegexp . ")*" . "(?:" . $this->mathOperatorRegexp . $this->mathRegexp . ")*";
		$this->modifierFuncRegexp = "([a-z_][\.\w]*)";
		
		$this->modifierArgRegexp = "([\"\']?).*\\4";
	}
	
	/**
	 * 取得完整的变量定义
	 *
	 * @return string
	 * @since 0.0.3
	 */
	public static function getWholeRegexp() {
		$var = new IMintVariable("");
		return $var->mixedVarAndMathRegexp . "(?:\|(?:" . "(?:[a-z_][\.\w]*)" . "(?:\((?:[,]?" . $var->modifierArgRegexp . ")*\))?" . "))*(?:" . $var->mathOperatorRegexp . $var->mathRegexp . ")*";
	}
	
	/**
	 * 从参数描述中分解参数
	 *
	 * @param string $argumentString 参数描述，用逗号分割每个参数
	 * @return array
	 * @since 0.0.3
	 */
	public function getArguments($argumentString) {
		$pieces = preg_split("/,/", $argumentString);
		$arguments = array();
		$last = null;
		foreach ($pieces as $index=>$piece) {
			//echo $piece . "<br/>";
			$match = array();
			if ($piece == "''" || $piece == "\"\"") {
				$arguments[] = "\"\"";
			}
			else if (preg_match("/^(" . $this->mathRegexp . "(" . $this->mathOperatorRegexp . ")?)+$/", $piece)) {
				$arguments[] = preg_replace("/[^\d]+$/", "", $piece);
			}
			else if (preg_match("/^([\"\']).*[^\\\]\\1$/", $piece, $match)) {
				$quote = $match[1];
				$arguments[] = $this->addslashes($piece);
			}
			else {
				if ($last === null) {
					$last .= $piece;
				}
				else {
					$last .= "," . $piece;
				}
				if (preg_match("/^([\"\']).*[^\\\]\\1$/", $last, $match)) {
					$quote = $match[1];
					$arguments[] = $this->addslashes($last);
					$last = null;
				}
			}
		}
		return $arguments;
	}
	
	/**
	 * 给字符串定义中的引号加入反斜杠（\）
	 * 
	 * 不转换字符串定义字符串，比如"'''"'""，加入\后，变成"'''\"'\""，其中的'并未处理
	 * 
	 * @param string $string 字符串定义，例如"i am string"
	 * @return string
	 * @since 0.0.3
	 */
	public function addslashes($string) {
		$match = array();
		preg_match("/^([\"\']).*\\1$/", $string, $match);
		$quote = $match[1];
		return $quote . preg_replace("/{$quote}/", "\\1\\" . $quote, substr($string, 1, -1)) . $quote;
	}
	
	/**
	 * 从变量定义中取得各个片段
	 *
	 * @param string $varDescription 变量定义
	 * @param boolean $containsMath 是否包含数学运算
	 * @param boolean $containsSlice 是否包含截取片段的语法，如$string[1,2]
	 * @return array
	 * @since 0.0.3
	 */
	public function getFragments($varDescription, $containsMath = true, $containsSlice = true) {
		//echo $varDescription . "<br/>";
		$pieces = array();
		preg_match_all("/()(\w+|" . $this->mixedVarRegexp . "|" . $this->mathOperatorRegexp . $this->mathRegexp . ")/i", $varDescription, $pieces);
		$contextPieces = $pieces[0];
		foreach ($contextPieces as $index=>$piece) {
			if (preg_match("/^" . $this->mathOperatorRegexp . $this->mathRegexp . "$/i", $piece) && $containsMath) {//math
				$contextPieces[$index] = $piece;
			}
			else if (preg_match("/^" . $this->varRegexp . "$/i", $piece)) {//var
				$contextPieces[$index] = $piece;
			}
			else if (preg_match("/^" . $this->arraySliceRegexp . "$/i", $piece) && $containsSlice) {//slice
				$tmpVarname = $this->getUniqueId();
				$match = array();
				preg_match("/^\[\s*(\d+)\s*\,\s*(\d+)\s*\]$/i", $piece, $match);
				$dataName = self::getDataName($tmpVarname);
				$this->parseProcess .= $dataName . "=\$TDataSet->" . implode("", array_slice($contextPieces, 0, $index)) . ";\n";
				$length = max(0, $match[2] - $match[1]+1);
				$this->parseProcess .=  "if(is_array({$dataName})){\n" . $dataName . "=array_slice({$dataName},{$match[1]},{$length},true);\n}else if (is_scalar({$dataName})){\n{$dataName}=substr({$dataName},{$match[1]},{$length});\n}\n";
				$varDescription = "\$" . $tmpVarname . implode("", array_slice($contextPieces, $index+1));
				return $this->getFragments($varDescription);
			}
			else if (preg_match("/^()(" . $this->arrayRegexp . ")$/i", $piece, $match)) {//array
				if ($match[3] == "") {
					if (!preg_match("/^\[" . $this->mathRegexp . "\]$/i", $piece)) {
						$piece = "[\"" . addslashes(substr($piece, 1, -1)) . "\"]";
					}
				}
				$contextPieces[$index] = $piece;
			}
			else if (preg_match("/^" . $this->objectRegexp . "$/i", $piece)) {//object
				$contextPieces[$index] = $piece;
			}
			else if (preg_match("/^" . $this->dotObjectRegexp . "$/i", $piece)) { //dotObject
				$contextPieces[$index] = str_replace(".", "->", $piece);
			}
		}	
		return $contextPieces;		
	}
	
	/**
	 * 取得变量定义
	 *
	 * @return string
	 * @since 0.0.3
	 */
	public function getDescription() {
		return $this->description;
	}
	
	/**
	 * 取得分析过程
	 *
	 * 此过程应该在变量被使用之前执行
	 * 
	 * @return string
	 * @since 0.0.3
	 */
	public function getParseProcess() {
		return $this->parseProcess;
	}

	/**
	 * 取得分析后的变量名
	 *
	 * @return string
	 * @since 0.0.2
	 */
	public function getVarname() {
		return $this->varname;
	}
	
	/**
	 * 取得一个不重复的Id
	 * 
	 * 可以用于变量和方法命名，如
	 * - _472930b7550aa936549821
	 * - _472930b754edd373927939
	 * - ...
	 *
	 * @return string
	 * @since 0.0.3
	 */
	public function getUniqueId() {
		return "_" . str_replace(".", "", uniqid(null, true));
	}
	
	/**
	 * 判断变量定义是否正确
	 *
	 * @return boolean
	 * @since 0.0.3
	 */
	public function isValid() {
		return $this->valid;
	}
	
	/**
	 * 根据变量名取得实际的数据名称
	 *
	 * @param string $varname 变量名
	 * @return string
	 * @since 0.0.3
	 */
	public static function getDataName($varname) {
		return "\$TDataSet->" . $varname;
	}
	
	/**
	 * 取得变量描述中定义部分
	 *
	 * @param string $description 变量描述
	 * @return string
	 * @since 0.0.3
	 */
	public static function getDefinition($description) {
		$var = new IMintVariable($description);
		if (!$var->isValid()) {
			return null;
		}
		return self::getDataName(implode("", $var->getFragments($description, false, false)));
	}
}


?>